/////////////////////////////////////////////////////////////////////////////
// Name:        checkedlistctrl.cpp
// Purpose:     wxCheckedListCtrl
// Author:      Uknown ? (found at http://wiki.wxwidgets.org/wiki.pl?WxListCtrl)
// Modified by: Francesco Montorsi
// Created:     2005/06/29
// RCS-ID:      $Id$
// Copyright:   (c) 2005 Francesco Montorsi
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////




// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

// includes
#include "wx/checkedlistctrl.h"
#include <wx/icon.h>
#include <wx/settings.h>


#if wxUSE_CHECKEDLISTCTRL

#if !CLC_VBAM_USAGE || !CLC_USE_SYSICONS
// resources
#include "wx/checked.xpm"
#include "wx/unchecked.xpm"
#include "wx/checked_dis.xpm"
#include "wx/unchecked_dis.xpm"
#else
#include <wx/dcbuffer.h>
#include <wx/renderer.h>
#include <wx/checkbox.h>
#endif

#if CLC_VBAM_USAGE
IMPLEMENT_DYNAMIC_CLASS(wxCheckedListCtrl, wxListCtrl)
#else
IMPLEMENT_CLASS(wxCheckedListCtrl, wxListCtrl)
#endif
BEGIN_EVENT_TABLE(wxCheckedListCtrl, wxListCtrl)
	EVT_LEFT_DOWN(wxCheckedListCtrl::OnMouseEvent)
END_EVENT_TABLE()

DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_CHECKED);
DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_UNCHECKED);



// ------------------
// wxCHECKEDLISTCTRL
// ------------------

bool wxCheckedListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint &pt,
                               const wxSize &sz, long style, const wxValidator &validator, const wxString &name)
{
	if (!wxListCtrl::Create(parent, id, pt, sz, style, validator, name))
		return FALSE;

#if CLC_VBAM_USAGE
	// support xrc by making init separate
	return Init();
}

bool wxCheckedListCtrl::Init()
{
#if CLC_USE_SYSICONS
	// use native size images instead of 16x16
	wxCheckBox* cb = new wxCheckBox(GetParent(), wxID_ANY, wxEmptyString);
	wxSize cbsz = cb->GetBestSize();
	delete cb;
	m_imageList.Create(cbsz.GetWidth(), cbsz.GetHeight(), TRUE);
#else
	m_imageList.Create(16, 16, TRUE);
#endif
#endif
	SetImageList(&m_imageList, wxIMAGE_LIST_SMALL);
#if CLC_VBAM_USAGE && CLC_USE_SYSICONS
	// pasted from wxWiki
	// but with native size instead of 16x16
	// constructor only takes wxSize in 2.9+, apparently
	wxBitmap unchecked_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
	         checked_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
	         unchecked_disabled_bmp(cbsz.GetWidth(), cbsz.GetHeight()),
	         checked_disabled_bmp(cbsz.GetWidth(), cbsz.GetHeight());
	// Bitmaps must not be selected by a DC for addition to the image list but I don't see
	// a way of diselecting them in wxMemoryDC so let's just use a code block to end the scope
	{
		wxMemoryDC renderer_dc;
		// Unchecked
		renderer_dc.SelectObject(unchecked_bmp);
		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
		renderer_dc.Clear();
		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), 0);
		// Checked
		renderer_dc.SelectObject(checked_bmp);
		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
		renderer_dc.Clear();
		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), wxCONTROL_CHECKED);
		// Unchecked and Disabled
		renderer_dc.SelectObject(unchecked_disabled_bmp);
		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
		renderer_dc.Clear();
		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), 0 | wxCONTROL_DISABLED);
		// Checked and Disabled
		renderer_dc.SelectObject(checked_disabled_bmp);
		renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
		renderer_dc.Clear();
		wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, 16, 16), wxCONTROL_CHECKED | wxCONTROL_DISABLED);
	}
	// the add order must respect the wxCLC_XXX_IMGIDX defines in the headers !
	m_imageList.Add(unchecked_bmp);
	m_imageList.Add(checked_bmp);
	m_imageList.Add(unchecked_disabled_bmp);
	m_imageList.Add(checked_disabled_bmp);
#else
	// the add order must respect the wxCLC_XXX_IMGIDX defines in the headers !
	m_imageList.Add(wxIcon(unchecked_xpm));
	m_imageList.Add(wxIcon(checked_xpm));
	m_imageList.Add(wxIcon(unchecked_dis_xpm));
	m_imageList.Add(wxIcon(checked_dis_xpm));
#endif
	return TRUE;
}

/* static */
int wxCheckedListCtrl::GetItemImageFromAdditionalState(int addstate)
{
	bool checked = (addstate & wxLIST_STATE_CHECKED) != 0;
	bool enabled = (addstate & wxLIST_STATE_ENABLED) != 0;

	if (checked && enabled)
		return wxCLC_CHECKED_IMGIDX;
	else if (checked && !enabled)
		return wxCLC_DISABLED_CHECKED_IMGIDX;
	else if (!checked && enabled)
		return wxCLC_UNCHECKED_IMGIDX;

	wxASSERT(!checked && !enabled);     // this is the last possibility
	return wxCLC_DISABLED_UNCHECKED_IMGIDX;
}

wxColour wxCheckedListCtrl::GetBgColourFromAdditionalState(int additionalstate)
{
	if ((additionalstate & wxLIST_STATE_ENABLED) &&
	        this->IsEnabled())
		return *wxWHITE;

#ifdef __WXMSW__
	return wxColour(212, 208, 200);
#else
	return wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
#endif
}

/* static */
int wxCheckedListCtrl::GetAndRemoveAdditionalState(long* state, int statemask)
{
	int additionalstate = 0;

	if (!state) return -1;

	// extract the bits we are interested in
	bool checked = (*state & wxLIST_STATE_CHECKED) != 0;
	bool enabled = (*state & wxLIST_STATE_ENABLED) != 0;

	// and set them in a different variable if they are included in the statemask
	if (checked && (statemask & wxLIST_STATE_CHECKED)) additionalstate |= wxLIST_STATE_CHECKED;

	if (enabled && (statemask & wxLIST_STATE_ENABLED)) additionalstate |= wxLIST_STATE_ENABLED;

	// remove them from the original state var...
	*state &= ~wxLIST_STATE_CHECKED;
	*state &= ~wxLIST_STATE_ENABLED;
	return additionalstate;
}

bool wxCheckedListCtrl::GetItem(wxListItem &info) const
{
	// wx internal wxListCtrl::GetItem remove from the state mask the
	// wxLIST_STATE_CHECKED & wxLIST_STATE_ENABLED bits since they
	// are not part of wx standard flags... so we need to check those
	// flags against the original wxListItem's statemask...
	wxListItem original(info);
#ifdef __WXDEBUG__
	// we always want to retrieve also the image state for checking purposes...
	info.m_mask |= wxLIST_MASK_IMAGE;
#endif

	if (!wxListCtrl::GetItem(info))
		return FALSE;

	// these are our additional supported states: read them from m_stateList
	bool checked = (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED) != 0;
	bool enabled = (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED) != 0;

	// now intercept state requests about enable or check mode
	if ((original.m_mask & wxLIST_MASK_STATE) &&
	        (original.m_stateMask & wxLIST_STATE_CHECKED))
	{
		info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED);
		info.m_stateMask |= wxLIST_STATE_CHECKED;
		info.m_mask |= wxLIST_MASK_STATE;       // contains valid info !
	}

	if ((original.m_mask & wxLIST_MASK_STATE) &&
	        (original.m_stateMask & wxLIST_STATE_ENABLED))
	{
		info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED);
		info.m_stateMask |= wxLIST_STATE_ENABLED;
		info.m_mask |= wxLIST_MASK_STATE;       // contains valid info !
	}

	// check that state & image are synch
#if CLC_VBAM_USAGE

	if (info.GetColumn())
		return TRUE;

#endif
#ifdef __WXDEBUG__
	wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(),
	             wxT("Something wrong ! See InsertItem()"));
	// read info by image index
	bool imagecheck = (info.m_image == wxCLC_CHECKED_IMGIDX) ||
	                  (info.m_image == wxCLC_DISABLED_CHECKED_IMGIDX);
	bool imageenabled = (info.m_image == wxCLC_CHECKED_IMGIDX) ||
	                    (info.m_image == wxCLC_UNCHECKED_IMGIDX);
	wxASSERT_MSG((checked && imagecheck) || (!checked && !imagecheck),
	             wxT("This is item has checked state but it's shown as unchecked (or viceversa)"));
	wxASSERT_MSG((enabled && imageenabled) || (!enabled && !imageenabled),
	             wxT("This is item has enabled state but it's shown as disabled (or viceversa)"));
#endif
	return TRUE;
}

bool wxCheckedListCtrl::SetItem(wxListItem &info)
{
#if CLC_VBAM_USAGE

	// only col 0 gets a checkbox
	if (info.GetColumn())
		return wxListCtrl::SetItem(info);

#endif
	// remove the checked & enabled states from the state flag:
	// we'll store them in our separate array
	int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask);

	// set image index
	// we will ignore the info.m_image field since we need
	// to overwrite it...
	if (info.m_mask & wxLIST_MASK_STATE)
	{
		// if some state is not included in the state mask, then get the state info
		// from our internal state array
		if (!(info.m_stateMask & wxLIST_STATE_ENABLED))
			additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED);

		if (!(info.m_stateMask & wxLIST_STATE_CHECKED))
			additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED);

		// state is valid: use it to determine the image to set...
		info.m_mask |= wxLIST_MASK_IMAGE;
		info.m_image = GetItemImageFromAdditionalState(additionalstate);
		// since when changing the background color, also the foreground color
		// and the font of the item are changed, we try to respect the user
		// choices of such attributes
		info.SetTextColour(this->GetItemTextColour(info.GetId()));
#if wxCHECK_VERSION(2, 6, 2)
		// before wx 2.6.2 the wxListCtrl::SetItemFont function is missing
		info.SetFont(this->GetItemFont(info.GetId()));
#endif
		// change the background color to respect the enabled/disabled status...
		info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate));
		m_stateList[info.m_itemId] = additionalstate;
	}
	else
	{
		// state is invalid; don't change image
		info.m_mask &= ~wxLIST_MASK_IMAGE;
	}

	// save the changes
	return wxListCtrl::SetItem(info);
}

long wxCheckedListCtrl::InsertItem(wxListItem &info)
{
	int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask);

	if (!(info.m_mask & wxLIST_MASK_STATE) ||
	        !(info.m_stateMask & wxLIST_STATE_ENABLED))
	{
		// if not specified, the default additional state is ENABLED
		additionalstate = wxLIST_STATE_ENABLED;
	}

	// we always want to insert items with images...
	info.m_mask |= wxLIST_MASK_IMAGE;
	info.m_image = GetItemImageFromAdditionalState(additionalstate);
	info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate));
	int itemcount = GetItemCount();
	wxASSERT_MSG(info.m_itemId <= itemcount, wxT("Invalid index !"));
	wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(),
	             wxT("Something wrong !"));

	if (info.m_itemId == itemcount)
	{
		// we are adding a new item at the end of the list
		m_stateList.Add(additionalstate);
	}
	else
	{
		// we must shift all following items
		m_stateList.Add(m_stateList[itemcount - 1]);

		for (int i = itemcount - 1; i > info.m_itemId; i--)
			m_stateList[i] = m_stateList[i - 1];

		m_stateList[info.m_itemId] = additionalstate;
	}

	return wxListCtrl::InsertItem(info);
}

bool wxCheckedListCtrl::SetItemState(long item, long state, long stateMask)
{
	wxListItem li;
	li.SetId(item);
	li.SetMask(wxLIST_MASK_STATE);
	li.SetState(state);
	li.SetStateMask(stateMask);
	// so we are sure to use wxCheckedListCtrl::SetItem
	// (and not wxListCtrl::SetItem)
	return SetItem(li);
}

int wxCheckedListCtrl::GetItemState(long item, long stateMask) const
{
	wxListItem li;
	li.SetId(item);
	li.SetMask(wxLIST_MASK_STATE);
	li.SetStateMask(stateMask);

	// so we are sure to use wxCheckedListCtrl::GetItem
	// (and not wxListCtrl::GetItem)
	if (!GetItem(li))
		return -1;

	return li.GetState();
}

long wxCheckedListCtrl::SetItem(long index, int col, const wxString &label, int WXUNUSED(imageId))
{
	wxListItem li;
	li.SetId(index);
	li.SetColumn(col);
	li.SetText(label);
	li.SetMask(wxLIST_MASK_TEXT);
	// so we are sure to use wxCheckedListCtrl::SetItem
	// (and not wxListCtrl::SetItem)
	return SetItem(li);
}

long wxCheckedListCtrl::InsertItem(long index, const wxString &label, int WXUNUSED(imageIndex))
{
	wxListItem info;
	info.m_text = label;
	info.m_mask = wxLIST_MASK_TEXT;
	info.m_itemId = index;
	return InsertItem(info);
}

void wxCheckedListCtrl::Check(long item, bool checked)
{
	// NB: the "statemask" is not the "mask" of a list item;
	//     in the "mask" you use the wxLIST_MASK_XXXX defines;
	//     in the "statemask" you use the wxLIST_STATE_XXX defines
	//     to set a specific bit of the wxListInfo::m_state var
	if (checked)
		// the 2nd parameter says: activate the STATE bit relative to CHECK feature
		// the 3rd parameter says: set only *that* bit
		SetItemState(item, wxLIST_STATE_CHECKED, wxLIST_STATE_CHECKED);
	else
		SetItemState(item, 0, wxLIST_STATE_CHECKED);
}

void wxCheckedListCtrl::Enable(long item, bool enable)
{
	if (enable)
		// the 2nd parameter says: activate the STATE bit relative to ENABLE feature
		// the 3rd parameter says: set only *that* bit
		SetItemState(item, wxLIST_STATE_ENABLED, wxLIST_STATE_ENABLED);
	else
		SetItemState(item, 0, wxLIST_STATE_ENABLED);
}

void wxCheckedListCtrl::EnableAll(bool enable)
{
	for (int i = 0; i < GetItemCount(); i++)
		Enable(i, enable);
}

void wxCheckedListCtrl::CheckAll(bool check)
{
	for (int i = 0; i < GetItemCount(); i++)
		Check(i, check);
}

bool wxCheckedListCtrl::DeleteItem(long item)
{
	// shift our additional state array
	//for (int i=item,max=GetItemCount(); i < max-1; i++)
	//  m_stateList[i] = m_stateList[i+1];
	m_stateList.RemoveAt(item, 1);
	return wxListCtrl::DeleteItem(item);
}

int wxCheckedListCtrl::GetCheckedItemCount() const
{
	int res = 0;

	for (int i = 0; i < GetItemCount(); i++)
		if (IsChecked(i))
			res++;

	return res;
}

// event handlers

void wxCheckedListCtrl::OnMouseEvent(wxMouseEvent &event)
{
	if (!event.LeftDown())
	{
		event.Skip();
		return;
	}

	int flags;
	long item = HitTest(event.GetPosition(), flags);

	if (item == wxNOT_FOUND || !IsEnabled(item))
	{
		// skip this item
		event.Skip();
		return;
	}

	// user clicked exactly on the checkbox or on the item row ?
	bool processcheck = (flags & wxLIST_HITTEST_ONITEMICON) ||
	                    ((GetWindowStyle() & wxCLC_CHECK_WHEN_SELECTING) &&
	                     (flags & wxLIST_HITTEST_ONITEM));

	if (processcheck)
	{
		wxListEvent ev(wxEVT_NULL, GetId());
		ev.m_itemIndex = item;

		// send the check event
		if (IsChecked(item))
		{
			ev.SetEventType(wxEVT_COMMAND_LIST_ITEM_UNCHECKED);
			Check(item, FALSE);
			AddPendingEvent(ev);
		}
		else
		{
			ev.SetEventType(wxEVT_COMMAND_LIST_ITEM_CHECKED);
			Check(item, TRUE);
			AddPendingEvent(ev);
		}
	}

	event.Skip();
}

#endif      // wxUSE_CHECKEDLISTCTRL
